#include "topsail.h"
#include "server.h"
#include "wraps.h"
#include "error.h"
#include "utils.h"
#include "config.h"

static void on_duty();
static int server_listen();
static int load_config(char *configfile);
static int parse_options(int argc, char **argv);
static void show_usage(void);
static void service(int connfd);
static void parse_request(int connfd, struct http_request *req);
static void show_directory(int connfd, char *path);
static int typecmp(const void *m1, const void *m2);
static char *get_mimetype(char *postfix);
static void send_file(int connfd, char *path, long filesize, char *mimetype);
static void error_occur(int connfd, int status, char *title, char *msg);
static int become_daemon(void);
static void cgi_process(int connfd, char *cgi);
static char *has_indexfile(char *dir);

struct type_map {
	char *postfix;
	char *mimetype;
} type_maps[] = {   // caution: postfixs in type_maps must be order(ascend)!
	{"", 			"application/octet-stream"},
	{".asc", 		"text/plain"},
	{".asf", 		"video/x-ms-asf"},
	{".asx", 		"video/x-ms-asf"},
	{".avi", 		"video/x-msvideo"},
	{".bz2", 		"application/x-bzip"},
	{".c", 			"text/plain"},
	{".class", 		"application/octet-stream"},
	{".conf", 		"text/plain"},
	{".cpp", 		"text/plain"},
	{".css", 		"text/css"},
	{".dtd", 		"text/xml"},
	{".dvi", 		"application/x-dvi"},
	{".gif", 		"image/gif"},
	{".gz", 		"application/x-gzip"},
	{".htm", 		"text/html"},
	{".html", 		"text/html"},
	{".jar", 		"application/x-java-archive"},
	{".jpeg", 		"image/jpeg"},
	{".jpg", 		"image/jpeg"},
	{".js", 		"text/javascript"},
	{".log", 		"text/plain"},
	{".m3u", 		"audio/x-mpegurl"},
	{".mov", 		"video/quicktime"},
	{".mp3", 		"audio/mpeg"},
	{".mpeg", 		"video/mpeg"},
	{".mpg", 		"video/mpeg"},
	{".ogg", 		"application/ogg"},
	{".pac", 		"application/x-ns-proxy-autoconfig"},
	{".pdf", 		"application/pdf"},
	{".png", 		"image/png"},
	{".ps", 		"application/postscript"},
	{".qt", 		"video/quicktime"},
	{".sig", 		"application/pgp-signature"},
	{".spl", 		"application/futuresplash"},
	{".swf", 		"application/x-shockwave-flash"},
	{".tar", 		"application/x-tar"},
	{".tar.bz2", 	"application/x-bzip-compressed-tar"},
	{".tar.gz", 	"application/x-tgz"},
	{".tbz", 		"application/x-bzip-compressed-tar"},
	{".text", 		"text/plain"},
	{".tgz", 		"application/x-tgz"},
	{".torrent", 	"application/x-bittorrent"},
	{".txt", 		"text/plain"},
	{".wav", 		"audio/x-wav"},
	{".wax", 		"audio/x-ms-wax"},
	{".wma", 		"audio/x-ms-wma"},
	{".wmv", 		"video/x-ms-wmv"},
	{".xbm", 		"image/x-xbitmap"},
	{".xml", 		"text/xml"},
	{".xpm", 		"image/x-xpixmap"},
	{".xwd", 		"image/x-xwindowdump"},
	{".zip", 		"application/zip"}
};

#define nr_of_types (sizeof(type_maps) / sizeof(type_maps[0]))

int main(int argc, char **argv)
{
	/* to be a daemon process */
	become_daemon();

	/* parse command-line options */
	parse_options(argc, argv);
	
	/* set working directory */
	Chdir(hs.rootdir, __FILE__, __LINE__);

	/* server start up */
	on_duty();
	
	exit(EXIT_SUCCESS);
}

static void on_duty()
{
	pid_t childpid;
	int listenfd;
	int connfd;

	listenfd = server_listen();
	for (; ;) {
		connfd = Accept(listenfd, (SA *)NULL, NULL, __FILE__, __LINE__);
		if ( (childpid = Fork(__FILE__, __LINE__)) == 0) { /* child process */
			Close(listenfd, __FILE__, __LINE__); /* child must close listening socket*/
			service(connfd);
			Close(connfd, __FILE__, __LINE__);
			exit(EXIT_SUCCESS);
		} else {
			Close(connfd, __FILE__, __LINE__); /* parent must closes connected socket */
			wait(NULL);
		}
	}
}

static int become_daemon(void)
{
	pid_t pid;
	int fd;

	pid = Fork(__FILE__, __LINE__);
	if ( 0 != (pid = Fork(__FILE__, __LINE__)))
		exit(EXIT_SUCCESS); /* parent goes bye-bye */
	
	setsid();
	if ( 0 != (pid = Fork(__FILE__, __LINE__)))
		exit(EXIT_SUCCESS);

	umask(0);
	for (fd = 0; fd < 64; fd++)
		close(fd);

	return 0;
}

static void service(int connfd)
{
	struct http_request req;
	struct stat buf;
	
	parse_request(connfd, &req);
	if (lstat(req.url, &buf) == -1) {
		if (errno == ENOENT) { /* the request file is not exist */
			error_occur(connfd, 404, "Not Found", "");
			return;
		}
	}
	
	if ( (strstr(req.url, "/cgi-bin/") - req.url) == 1) { /* cgi process */
		cgi_process(connfd, req.url);	
	} else if (S_ISREG(buf.st_mode)) { /* is regular file */
		send_file(connfd, req.url, buf.st_size, get_mimetype(strrchr(req.url, '.')));		
	} else if (S_ISDIR(buf.st_mode)) { /* is directory */
		char *indexfile;
		if ( (indexfile = has_indexfile(req.url)) != NULL ) {
			char indexbuf[LINE_BUF_SIZE];
			strcpy(indexbuf, req.url);
			if (req.url[strlen(req.url) - 1] != '/')
				strcpy(indexbuf, "/");
			strcat(indexbuf, indexfile);
			send_file(connfd, indexbuf, 1024, get_mimetype(strrchr(indexbuf, '.')));
		} else {
			show_directory(connfd, req.url);
		}
	} else {
		error_occur(connfd, 404, "Not Found", "");
	}
}

static char *has_indexfile(char *dir)
{	
	char idx_files[LINE_BUF_SIZE];
	strcpy(idx_files, hs.indexfiles);

	char *delim = ", ";

	char *file = strtok(idx_files, delim);
	if (file_exist(dir, file))
		return file;

	while ( (file = strtok(NULL, delim))) {
		if (file_exist(dir, file))
			return file;
	}
	return NULL;
}

static void cgi_process(int connfd, char *cgi)
{
	FILE *fp;
	char buf[LINE_BUF_SIZE];
	int n;

	sendmime(connfd, 200, "OK", 1024);
	fp = popen(cgi, "r");
	while ( (n = fread(buf, 1, sizeof(buf), fp)) > 0)
		writen(connfd, buf, n);
}

static void send_file(int connfd, char *path, long filesize, char *mimetype)
{
	char buf[LINE_BUF_SIZE];
	int fd;
	int nbytes;
	
	sendmime(connfd, 200, "OK", filesize);
	sendline(connfd, "Content-type: %s charset=iso-8859-1%s%s", mimetype, CRLF, CRLF);

	fd = Open(path, O_RDONLY, __FILE__, __LINE__);
	while ( ( nbytes = read(fd, buf, sizeof(buf))) != 0) {
		writen(connfd, buf, nbytes);
	}
	Close(fd, __FILE__, __LINE__);
}

static void show_directory(int connfd, char *path)
{

	sendmime(connfd, 200, "OK", 1024);
	sendline(connfd, "Conten-type: text/html; charset=iso-8859-1%s%s", CRLF, CRLF);
	
	DIR *dp;
	struct dirent *entry;
	
	sendline(connfd, 
"<html>\n"
"<head><title>Index of %s </title></head>\n"
"<body>\n"
"<h1><b>Index of %s</b></h1><hr>\n"
"<ul>\n" , path, path);

	
	dp = Opendir(path, __FILE__, __LINE__);
	sendline(connfd, "<li><a href=\"\">..</a></li>\n");

	while ( (entry = readdir(dp)) != NULL) {
		if (strcmp(".", entry->d_name) == 0 ||
			strcmp("..", entry->d_name) == 0)
			continue;
		sendline(connfd, "<li><a href=\"%s/%s\">%s</a></li>\n", path + 2, entry->d_name, entry->d_name);
	}
	Closedir(dp, __FILE__, __LINE__);

	sendline(connfd, 
"</ul><br /><hr />\n"
"<address>%s/%s</address>\n"
"</body>\n"
"</html>\n", SERVER_NAME, VERSION);

}

/* generate error-page */
static void error_occur(int connfd, int status, char *title, char *msg)
{
	sendmime(connfd, status, title, -1);
	sendline(connfd, "Content-type: text/html; charset=iso-8859-1%s%s", CRLF, CRLF);

	sendline(connfd, 
"<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"
"<head>\n"
"<title>%d - %s</title>\n"
"</head>\n"
"<body>\n"
"<h1>%d - %s</h1>\n"
"</body>\n"
"</html>\n", status, title, status, title);

}

static void parse_request(int connfd, struct http_request *req)
{
	char buf[LINE_BUF_SIZE];
	FILE *fp;

	fp = Fdopen(connfd, "r", __FILE__, __LINE__);
	if (fgets(buf, sizeof(buf), fp)) {
		/* Methods such as GET, POST etc. */
		sprintf(req->method, "%s", strtok(buf, " "));

		/* request file, we set it relative to root-dir. */
		sprintf(req->url, ".%s", strtok(NULL, " "));

		/* HTTP Protocol version. */
		sprintf(req->version, "%s", strtok(NULL, " "));
	
		DEBUG_MSG("METHOD=%s, URL=%s, VERSION=%s\n", req->method, req->url, req->version);
	}

	while (fgets(buf, sizeof(buf), fp)) {
		if (strncmp(buf, CRLF, 2) == 0)
			break;
		DEBUG_MSG("%s", buf);
	}
}

static int server_listen()
{
	int listenfd;
	struct sockaddr_in srvaddr;
	
	listenfd = Socket(AF_INET, SOCK_STREAM, 0, __FILE__, __LINE__);

	bzero(&srvaddr, sizeof(srvaddr));
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	srvaddr.sin_port = htons(hs.port);

	Bind(listenfd, (SA *)&srvaddr, sizeof(srvaddr), __FILE__, __LINE__);
	Listen(listenfd, LISTENQ, __FILE__, __LINE__);
	DEBUG_MSG("Topsail Started!\n");
	DEBUG_MSG("  root-dir: %s\n", hs.rootdir);
	DEBUG_MSG("  port    : %d\n", hs.port);
	return listenfd;
}

static int load_config(char *configfile)
{
	Config *config = config_create(configfile);

	/* set server document-root */
	sprintf(hs.rootdir, config_get_value(config, "DOCUMENT_ROOT"));

	/* set server listening port */
	hs.port = atoi(config_get_value(config, "LISTEN_PORT"));

	/* set server pid to pid-file */
	log_pid(config_get_value(config, "PID_FILE"));

	/* set index files */
	strcpy(hs.indexfiles, config_get_value(config, "INDEX_FILES"));
	/* printf("%s\n", config_get_value(config, "ACCESS_LOG")); */
	
	config_destroy(config);
	return 0;
}

static int parse_options(int argc, char **argv)
{
	int opt;
	if (argc <= 1) {
		show_usage();
		exit(EXIT_FAILURE);
	}
	
	while ( (opt = getopt(argc, argv, "f:")) != -1) {
		switch (opt) {
		case 'f':
			load_config(optarg);
			return 0;
			break;
		case '?':
			printf("unknown option: %c\n", optopt);
			return -1;
		}
	}

	show_usage();
	exit(EXIT_FAILURE);;
}

static void show_usage(void)
{
	fprintf(stdout, "+----------------------------------------------\n");
	fprintf(stdout, "| (Topsail) Http Server                        \n");
	fprintf(stdout, "|                                              \n");
	fprintf(stdout, "| version: %s                                  \n", VERSION);
	fprintf(stdout, "| author : Stevenzhou <tcaosmail@gmail.com>    \n");
	fprintf(stdout, "|                                              \n");
	fprintf(stdout, "| options:                                     \n");
	fprintf(stdout, "|   -f: configuration file.                    \n");
	fprintf(stdout, "|                                              \n");
	fprintf(stdout, "| example: ./topsail -f <config-file>          \n");
	fprintf(stdout, "+----------------------------------------------\n");
}

static char *get_mimetype(char *postfix)
{
	char *ret_type;

	struct type_map key;
	key.postfix = postfix;

	struct type_map *ret;
	ret = bsearch(&key, type_maps, nr_of_types,	sizeof(struct type_map), typecmp);

	if (ret == NULL) // default mimetype
		ret_type = "application/octet-stream";
	else 
		ret_type = ret->mimetype;
	
	return ret_type;
}

static int typecmp(const void *m1, const void *m2) {
	struct type_map *pent1 = (struct type_map *) m1;
	struct type_map *pent2 = (struct type_map *) m2;
	return strcmp(pent1->postfix, pent2->postfix);
}
